/*
This file is part of [aconnect] library. 

Author: Artem Kustikov (kustikoff[at]tut.by)


This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.
*/

#ifndef ACONNECT_GZIP_COMPRESSOR_H
#define ACONNECT_GZIP_COMPRESSOR_H

#include "../config.hpp"

#if defined (ACONNECT_USE_ZLIB)

#if defined(WIN32)
#	include <ctime>
#elif defined(__GNUC__)
#	include <sys/time.h>
#endif

#include "base_compressor.hpp"


namespace aconnect 
{	
	namespace compression
	{
		namespace zlib
		{
			namespace legacy
			{
#include "zlib.h"
#if defined (WIN32)
#	if defined(_DEBUG)
		#pragma comment(lib, "zlibd.lib")
#	else
		#pragma comment(lib, "zlib.lib")
#	endif
#endif
			}

			// Compression levels
			const int no_compression       = Z_NO_COMPRESSION;
			const int best_speed           = Z_BEST_SPEED;
			const int best_compression     = Z_BEST_COMPRESSION;
			const int default_compression  = Z_DEFAULT_COMPRESSION;

			// Compression methods

			const int deflated             = Z_DEFLATED;

			// Compression strategies

			const int default_strategy     = Z_DEFAULT_STRATEGY;
			const int filtered             = Z_FILTERED;
			const int huffman_only         = Z_HUFFMAN_ONLY;

			// Status codes

			const int okay                 = Z_OK;
			const int stream_end           = Z_STREAM_END;
			const int stream_error         = Z_STREAM_ERROR;
			const int version_error        = Z_VERSION_ERROR;
			const int data_error           = Z_DATA_ERROR;
			const int mem_error            = Z_MEM_ERROR;
			const int buf_error            = Z_BUF_ERROR;

			// Flush codes

			const int finish               = Z_FINISH;
			const int no_flush             = Z_NO_FLUSH;
			const int sync_flush           = Z_SYNC_FLUSH;

			const int default_window_bits  = 15;
			const int default_mem_level    = 8;


			const double default_content_ratio = 0.3;
			const size_t default_chunk_size = 1024 * 16;

			typedef legacy::uLong ulong;
			typedef legacy::Byte byte;
			typedef legacy::uInt uint;

			// GZIP related constants - copied from Boost.Iostreams
			namespace gzip
			{
				namespace magic {
					// Magic numbers used by gzip header.
					const char id1               = (char) 0x1f;
					const char id2               = (char) 0x8b;
				}

				namespace method {
					// Codes used for the 'CM' byte of the gzip header.
					const char deflate           = 8;
				}

				namespace flags {
					// Codes used for the 'FLG' byte of the gzip header.
					const char text              = 1;
					const char header_crc        = 2;
					const char extra             = 4;
					const char name              = 8;
					const char comment           = 16;
				} 

				namespace extra_flags {
					// Codes used for the 'XFL' byte of the gzip header.
					const char best_compression  = 2;
					const char best_speed        = 4;

				}
				
				namespace os {
					const char Fat            = 0;
					const char Amiga          = 1;
					const char Vms            = 2;
					const char Unix           = 3;
					const char VmCms          = 4;
					const char Atari          = 5;
					const char HPFS           = 6;
					const char Macintosh      = 7;
					const char ZSystem        = 8;
					const char CpM            = 9;
					const char Tops_20        = 10;
					const char NTFS           = 11;
					const char QDOS           = 12;
					const char Acorn          = 13;
					const char Unknown        = (char) 255;	
				}

			}

			struct GzipSettings
			{
				bool deflateMode;
				double ratio; // draft ratio between compressed/uncompressed data
				size_t chunkSize;

				int level;
				int method;
				int windowBits;
				int memLevel;
				int strategy;
				

				GzipSettings(	
					bool deflateMode    = true,
					double ratio		= default_content_ratio,
					size_t chunkSize	= default_chunk_size,

					int level           = default_compression,
					int method          = deflated, // one possible value
					int windowBits		= default_window_bits, 
					int memLevel		= default_mem_level, 
					int strategy        = default_strategy
					)
						: deflateMode(deflateMode), ratio(ratio), chunkSize(chunkSize),
						level(level), method(method), windowBits(windowBits),
						memLevel(memLevel), strategy(strategy)
				{ }
			};

			class GzipCompressor : public IBaseCompressor 
			{
			private:
				GzipCompressor() : _settings (GzipSettings()) { }
			public:
				GzipCompressor (const GzipSettings& settings) throw(application_error);
				
				//==================================================================//
				//	IBaseCompressor members											//
				//==================================================================//
				virtual void complete() throw(application_error); 

				virtual void compress(string_constptr content, const size_t contentSize, aconnect::string& storage, bool lastPart) throw(application_error);

				virtual size_t totalOut();
				//==================================================================//

				virtual ~GzipCompressor ();

				inline void setupFileInfo(aconnect::string_constptr fileName,
											std::time_t fileModificationTime,
											aconnect::string_constptr comment = NULL) 
				{
					if (fileName)
						_fileName = fileName;
					
					_fileModificationTime = fileModificationTime;

					if (comment)
						_comment = comment;
				}

			protected:
				void writeGzipHeader(aconnect::string& content);
				void writeGzipFooter(aconnect::string& content);

			protected:
				const GzipSettings _settings;
				char*	_buffer;
				legacy::z_stream _stream;

				bool _firstCompress;
				aconnect::string _fileName;
				aconnect::string _comment;
				std::time_t _fileModificationTime;

				// CRC for GZIP footer

				zlib::ulong _crc;
			};

		}
	}
}

#endif // #if defined (ACONNECT_USE_ZLIB)

#endif // ACONNECT_GZIP_COMPRESSOR_H
